/*
 * ******************************************************************************
 * Class: ServiceManagerHelper	
 * 
 * @author: Teresa Mafalda dos Reis Frazao
 * @version:April 10, 2014 
 * 
 * This class is responsible for performing all service operations.
 * These operations include: 1) creating and removing services, 2) get list of 
 * services, 3) get service details, 4) get services that comply with a condition,
 *  or request DependencyListUtils to persist operations in services.
 * ******************************************************************************
 */

package com.feedzai.service.utils;




import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import com.feedzai.service.data.ServiceNode;

/**
 * The Class ServiceManagerHelper.
 */
public class ServiceManagerHelper {
		
	/** Value - {@value}, The Constant 0. */
	private static final int ZERO = 0;
	
	/** Value - {@value}, The Constant false. */
	private static final Boolean FALSE = false;


	/**
	 * Instantiates a new service manager helper.
	 */
	public ServiceManagerHelper() {
	}
	
	
	/*
	 * ---------------------------------------------------------------------------
	 * 							Public Interface 
	 * ---------------------------------------------------------------------------
	 */
	
	
	/**
	 * Creates the service. To create a service we need to:
	 * <p>1) Create the ServiceNode
	 * <p>2) Initialize ServiceNode dependency list
	 * <p>3) Add the new service to Singleton.dependencyListUtils.serviceNodeHashMap
	 * <p>4) Set the service status to false in Singleton.dependencyListUtils.serviceNodeStatusHashMap
	 *
	 * @param dependencyList the is child from dependency list. 
	 * We do not fill is parent from list, since child nodes are added latter.
	 * @return the new service node
	 */
	public ServiceNode createService(HashMap<Long, ServiceNode> dependencyList)
	{
		//
		// 1. Create the service
		//
		ServiceNode serviceNode = new ServiceNode(Singleton.getInstance().getNewId());
				
		// If the object is created we can proceed to step 2)
		if(serviceNode != null)
		{
			// 
			// 2. Create dependency list
			// 
			addExistingDependencies(serviceNode, dependencyList);
			
			//
			// 3 and 4. Add service to singleton list and set it status to false
			//
			boolean isInRegister = addServiceNodeToServicesRegister(serviceNode);
			
			//
			// Thus, addNewService handles the cases where the node was not added.
			// However, if this function returns null and the operation had undo
			//
			if(!isInRegister)
			{
				// We discard the new object
				serviceNode = null;
			}
		}
		
		return serviceNode;
	}
	
	
	/**
	 * Adds the service dependency.
	 * 
	 * To add a dependency the <code>dependsOnList</code> and <code>isParentFromList</code> for 
	 * <code>childService</code> and <code>parentService</code> shall be set respectively. 
	 *
	 * @param parentServiceId the parent service id
	 * @param childServiceId the child service id
	 * @return the service node
	 */
	public synchronized ServiceNode addServiceDependency(Long parentServiceId, Long childServiceId)	
	{
		if(parentServiceId == null || childServiceId == null)
		{
			throw new IllegalArgumentException("Parent service or Child service cannot be null.");
		}
		
		Singleton.getInstance();
		ServiceNode parentService = Singleton.getDependencyListUtils().getAServiceNode(parentServiceId);
		ServiceNode childService = Singleton.getDependencyListUtils().getAServiceNode(childServiceId);
				
		if(parentService != null && childService != null && !parentService.equals(childService))
		{
			//
			// We need to update parent and child relations simultaneously
			// Like a double linked list, we keep a both ways relationship
			//
			childService.addIsChildFromDependency(parentService);
			parentService.addIsParentFromDependency(childService);
			
			//
			// Update registers in DependencyListUtils 
			//
			Singleton.getDependencyListUtils().updateService(childService);
			Singleton.getDependencyListUtils().updateService(parentService);
		}
		
		return childService;
	}
	
	
	/**
	 * Gets the parent nodes.
	 * 
	 * Parent nodes are nodes that are graph roots. Thus, there are no nodes they descend from.
	 *
	 * @return the parent nodes
	 */
	public LinkedList<ServiceNode> getParentNodes()
	{
		// Stores all parent nodes
		LinkedList<ServiceNode> parentNodeList = new LinkedList<ServiceNode>();
		
		//
		// We need first to get all nodes registered in DependencyListUtils. 
		// Then we check if they have any from relations. 
		//
		Singleton.getInstance();
		Iterator<ServiceNode> servicesList = Singleton.getDependencyListUtils().getAvailableServices().iterator(); 
		
		while (servicesList.hasNext()) 
		{
			ServiceNode serviceNode = (ServiceNode) servicesList.next();
			
			//
			// If a service has no registered parent nodes then it is a parent, so add it to the list.
			//
			if(serviceNode.getIsChildFromDependencies() == null || serviceNode.getIsChildFromDependencies().size() <= ZERO)
			{
				parentNodeList.add(serviceNode);
			}
		}
		
		return parentNodeList;
	}
	
	
	/**
	 * Gets the child nodes.
	 * 
	 * In this case the child nodes are graph nodes that are leaves in the graph's path.
	 *
	 * @return the child nodes
	 */
	public LinkedList<ServiceNode> getChildNodes()
	{
		// Stores all leaves nodes
		LinkedList<ServiceNode> childNodeList = new LinkedList<ServiceNode>();
		
		//
		// We need first to get all nodes registered in DependencyListUtils. 
		// Then we check if they have any to relations. 
		//
		Singleton.getInstance();
		Iterator<ServiceNode> servicesList = Singleton.getDependencyListUtils().getAvailableServices().iterator(); 
		
		while (servicesList.hasNext()) 
		{
			ServiceNode serviceNode = (ServiceNode) servicesList.next();
			
			//
			// If a service has no registered child nodes then it is a leave, so add it to the list.
			//
			if((serviceNode.getIsParentFromDependencies() == null) || (serviceNode.getIsParentFromDependencies().size() <= ZERO))
			{
				childNodeList.add(serviceNode);
			}
		}
		
		return childNodeList;
	}
		
	
	
	/**
	 * Can start.
	 * 
	 * A service node can start if:
	 * <p>1. All parent nodes are already in a start state.
	 * <p>2. The service is not in a running state.
	 * 
	 * <p>If the current not is not in <code>start</code> state we can start it.
	 * 
	 * <p>So we need to check all parent nodes, 
	 * if there is a single parent node which did not start, 
	 * we do not the current node.
	 *
	 * @param serviceNode the service node
	 * @return true, if successful
	 */
	public boolean canStart(ServiceNode serviceNode) 
	{
		if(serviceNode == null)
		{
			throw new IllegalArgumentException("The service node to start cannot be null.");
		}

		//
		// 2. Cannot start if the service is running.
		//
		if(Singleton.getDependencyListUtils().getAServiceNodeStatus(serviceNode.getId()))
		{
			return false;
		}
		
		LinkedList<Long> parentNodes = serviceNode.getIsChildFromDependencies();
		
		//
		// 1. I can start if I have no parent nodes.
		//
		if(parentNodes == null || parentNodes.size() <= ZERO)
		{
			return true;
		}
		
		//
		// 1. If I have parent nodes I need to check their state..
		//
		for(int i=0; i<parentNodes.size(); i++)
		{
			//
			// If there is a single parent that was not started we abort.
			//
			if(!Singleton.getDependencyListUtils().getAServiceNodeStatus(parentNodes.get(i)))
			{
				return false;
			}
		}
		
		// All conditions are fulfilled so I can start. 
		return true;
	}
	
	
	/**
	 * Can stop.
	 * 
	 * A service node can stop if:
	 * <p>1. All child nodes are already in a stop state.
	 * <p>2. I have no child nodes.
	 * 
	 * <p>If I have no child nodes I shall stop. 
	 * 
	 * <p>So we need to check all child nodes, 
	 * if there is a single child node which did not stop, we do not stop the current node.
	 *
	 * @param serviceNode the service node
	 * @return true, if successful
	 */
	public boolean canStop(ServiceNode serviceNode)	
	{
		if(serviceNode == null)
		{
			throw new IllegalArgumentException("Parent service to stop cannot be null.");
		}
		
		LinkedList<Long> childNodes = serviceNode.getIsParentFromDependencies();
		
		//
		// 2. Cannot stop if the service is not running.
		//
		if(!Singleton.getDependencyListUtils().getAServiceNodeStatus(serviceNode.getId()))
		{
			return false;
		}
		
		//
		// 1. If I have no parent nodes I can stop.
		//
		if(childNodes == null || childNodes.size() <= ZERO)
			return true;
		
		for(int i=0; i<childNodes.size(); i++)
		{
			//
			// If there is a single child that was not stop we abort.
			//
			if(Singleton.getDependencyListUtils().getAServiceNodeStatus(childNodes.get(i)))
				return false;
		}
		
		return true;
	}
	
	
	/**
	 * Start service.
	 * 
	 * To start a service we need:
	 * <p>1. Start the service thread.
	 * <p>2. Update service status.
	 * <p>3. Persist the information.
	 *
	 * @param serviceNode the service node
	 */
	public void startService(ServiceNode serviceNode)
	{
		// Start the thread
		serviceNode.start();
		
		// Persist changes in DependencyListUtils
		updateServiceStatusToServicesStatusRegister(serviceNode);
		Singleton.getDependencyListUtils().updateService(serviceNode);
	}
	
	
	/**
	 * Stop service.
	 * 
	 * To stop a service we need:
	 * <p>1. Stop the service thread.
	 * <p>2. Update service status.
	 * <p>3. Persist the information.
	 *
	 * @param serviceNode the service node
	 */
	public synchronized void stopService(ServiceNode serviceNode)
	{
		// Stop the thread
		serviceNode.stop();
		
		// Persist changes in DependencyListUtils
		updateServiceStatusToServicesStatusRegister(serviceNode);	
		Singleton.getDependencyListUtils().updateService(serviceNode);
	}
	
	
	/**
	 * Unmark all services.
	 * 
	 * Thus, set variable <code>visited</code> to <code>false</code> in all nodes.
	 */
	public void unmarkAllServices()
	{
		//
		// Get all services
		//
		Singleton.getInstance();
		Iterator<ServiceNode> servicesList = Singleton.getDependencyListUtils().getAvailableServices().iterator(); 
		
		while (servicesList.hasNext()) 
		{
			ServiceNode serviceNode = (ServiceNode) servicesList.next();
			
			//
			// Set visited to false, so that we can view the node.
			//
			serviceNode.setVisited(FALSE);
			Singleton.getDependencyListUtils().updateService(serviceNode);
		}
	}
	
	
	/**
	 * Gets a single unvisited node child or parent.
	 *
	 * @param serviceNode the service node
	 * @return the unvisited node
	 */
	public ServiceNode getUnvisitedNode(ServiceNode serviceNode, boolean isChildFrom) {

		if(serviceNode == null)
		{
			throw new IllegalArgumentException("Service cannot be null.");
		}
		
		LinkedList<Long> nodes = null;
		
		//
		// Gets a unvisited parent
		//
		if(isChildFrom) 
		{
			nodes = serviceNode.getIsChildFromDependencies();
		}
		
		//
		// Gets an unvisited child
		//
		else
		{
			nodes = serviceNode.getIsParentFromDependencies();
		}
		
		if(nodes != null)
		{
			return getUnvisitedNode(nodes);
		}
		
		return null;
	}
	
	
	/**
	 * Gets a list with unvisited node's child or parent relations.
	 *
	 * @param serviceNode the service node
	 * @return the unvisited node
	 */
	public LinkedList<ServiceNode> getAllUnvisitedNodes(ServiceNode serviceNode, boolean isChildFrom) {

		if(serviceNode == null)
		{
			throw new IllegalArgumentException("Service cannot be null.");
		}
		
		LinkedList<Long> nodes = null;
		
		//
		// Gets unvisited parents
		//
		if(isChildFrom) 
		{
			nodes = serviceNode.getIsChildFromDependencies();
		}
			
		//
		// Gets unvisited child nodes
		//
		else
		{
			nodes = serviceNode.getIsParentFromDependencies();
		}
		
		if(nodes != null)
			return getAllUnvisitedNodes(nodes);
		
		return null;
	}
	
	
	/**
	 * Mark service as visited.
	 *
	 * @param serviceNode the service node to set visited variable
	 */
	public void markService(ServiceNode serviceNode)
	{
		if(serviceNode == null)
		{
			throw new IllegalArgumentException("Cannot mark a service that is null.");
		}

		//
		// A service is visited if its state is true.
		//
		serviceNode.setVisited(true);

		//
		// Persist the information.
		//
		Singleton.getDependencyListUtils().updateService(serviceNode);
	}
	
	
	/*
	 * ---------------------------------------------------------------------------
	 * 							Private Interface 
	 * ---------------------------------------------------------------------------
	 */
	
	
	/**
	 * Adds the existing dependencies.
	 *
	 * @param serviceNode the service node
	 * @param dependencyList the dependency list
	 */
	private void addExistingDependencies(ServiceNode serviceNode, HashMap<Long, ServiceNode> dependencyList) 
	{
		//
		// There are no dependencies already assigned, but that is not a problem, since:
		// 1) The service is a parent service, so it has no dependencies
		// 2) The dependencies don't exist yet
		// 3) Dependencies will be included latter.
		//
		if(dependencyList == null || dependencyList.isEmpty())
			return;
		
		else
		{
			for(ServiceNode dependencyNode: dependencyList.values()) 
			{				
				serviceNode.addIsChildFromDependency(dependencyNode);
			}
		}
	}
	
	
	/**
	 * Adds the service node to services register.
	 *
	 * @param serviceNode the service node
	 * @return true, if successful
	 */
	private boolean addServiceNodeToServicesRegister(ServiceNode serviceNode)
	{
		
		Singleton.getInstance();
		Boolean addServiceToRegister = Singleton.getDependencyListUtils().addNewService(serviceNode);
		
		return addServiceToRegister;
	}
	
	
	/**
	 * Updates services status register by switching its state.
	 * It switches to status to <code>true</code> if current status if <code>false</code>. 
	 * Otherwise the status is set to <code>false</code>. 
	 *
	 * @param serviceNode the service node
	 * @return true, if successful
	 */
	private boolean updateServiceStatusToServicesStatusRegister(ServiceNode serviceNode)
	{
		Singleton.getInstance();
		Boolean updateServiceStatusRegister = Singleton.getDependencyListUtils().switchNodeStatus(serviceNode.getId());
		
		return updateServiceStatusRegister;
	}
	
	
	/**
	 * Gets the unvisited node.
	 * 
	 * An unvisited node, is a node where its <code>visible</code> state is set to <code>false</code>.
	 *
	 * @param nodes the nodes
	 * @return the unvisited node
	 */
	private ServiceNode getUnvisitedNode(LinkedList<Long> nodes) {

		ServiceNode unvisitedNode = null;
	
		for(int i=0; i<nodes.size(); i++)
		{
			unvisitedNode = Singleton.getDependencyListUtils().getAServiceNode(nodes.get(i));
			if(!unvisitedNode.isVisited())
				return unvisitedNode;
			
			unvisitedNode = null;
		}
		
		return unvisitedNode;
	}
	
	
	/**
	 * Gets all unvisited nodes.
	 *
	 * @param nodes the child or parent nodes to check the visibility.
	 * @return the all unvisited nodes
	 */
	private LinkedList<ServiceNode> getAllUnvisitedNodes(LinkedList<Long> nodes) {

		ServiceNode unvisitedNode = null;
		LinkedList<ServiceNode> unvisitedNodes = new LinkedList<ServiceNode>();
	
		for(int i=0; i<nodes.size(); i++)
		{
			unvisitedNode = Singleton.getDependencyListUtils().getAServiceNode(nodes.get(i));
			if(!unvisitedNode.isVisited())
				unvisitedNodes.add(unvisitedNode);
		}
		
		return unvisitedNodes;
	}
}
